"""
@Author    : ghenyar
@Time      : 2025/9/3 16:19
@File      : menu_service
@Desc      : 菜单权限
"""
import asyncio
from typing import List, Any, Set, Dict

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.admin.schemas.menus import MenuSerialize, MenuSchema
from app.admin.schemas.role import PermissionTree
from app.common.model_querier import ModelQuerier
from app.common.tools import build_tree, select_tree_orm
from app.models.menu_model import MenuModel
from app.utils.log import LoggingUtil


class MenuService:
    # 日志
    logger = LoggingUtil(f"{__name__}.MenuService")

    @classmethod
    async def lists(cls, db: AsyncSession) -> List[dict[str, Any]]:
        """
        菜单管理列表
        :param db: 数据库会话
        :return:
        """
        menu_list = await ModelQuerier(db, MenuModel).where(MenuModel.is_delete == False).order_by(
            MenuModel.sort.asc(),
            MenuModel.id.asc()
        ).get_all()

        # 序列化查询结果
        menus_list = [MenuSerialize.from_orm(menu) for menu in menu_list]
        menus_list = await asyncio.gather(*menus_list)

        # 构建树形结构
        tree = build_tree(menus_list, 0, "p_id")
        return tree

    @staticmethod
    async def get_menu_for_select(db: AsyncSession) -> List[dict[str, Any]]:
        """
        为下拉框获取菜单数据 去掉按钮操作
        :return:
        """
        menu_list = await ModelQuerier(db, MenuModel).where(MenuModel.is_delete == False,
                                                            MenuModel.types != 3).order_by(
            MenuModel.sort.asc(),
            MenuModel.id.asc()
        ).get_all()

        return select_tree_orm(menu_list, "label", "id")

    @classmethod
    async def save_add_edit(cls, db: AsyncSession, menuDto: MenuSchema) -> bool | Dict[str, str]:
        """
        创建或更新菜单
        :param db:
        :param menuDto:
        :return:
        """
        try:

            save_data = menuDto.model_dump(exclude_unset=True, exclude={"id"})

            p_label = "顶级菜单"
            if menuDto.p_id != 0:
                p_label = await cls.get_menu_label_by_id(db, menuDto.p_id)

            save_data["p_label"] = p_label

            if menuDto.id is  None or menuDto.id ==0:
                # 新增数据
                menu = MenuModel(**save_data)
                db.add(menu)
                await db.commit()
                return True
            else:
                # 更新数据
                up_success = await ModelQuerier(db, MenuModel).where(MenuModel.id == menuDto.id).update_one(**save_data)
                return bool(up_success)

        except Exception as e:
            await db.rollback()
            cls.logger.error(str(e))
            return {"message": str(e)}

    @classmethod
    async def get_menu_label_by_id(cls, db: AsyncSession, m_id: int) -> str | None:
        """
        根据 id获取菜单标题
        :param db: 数据库会话
        :param m_id: 菜单id
        :return:
        """
        menu = await ModelQuerier(db, MenuModel).only(MenuModel.label).get_by_id(m_id)
        if menu:
            return menu.label
        return None

    @classmethod
    async def delete_menu(cls, db: AsyncSession, m_id: int) -> bool | dict | None:
        """
        删除菜单数据
        :param db: 数据库会话
        :param m_id: 菜单id
        :return:
        """
        menu = await ModelQuerier(db, MenuModel).where(MenuModel.id == m_id).get_one()
        if not menu:
            return {"message": "删除失败，当前数据不存在", "error": -1}

        types_map = {1: "目录", 2: "菜单", 3: "按钮"}

        has_child = await cls.check_children(db, m_id)

        if has_child:
            return {"message": f"删除失败，当前{types_map[menu.types]}存在{types_map[menu.types + 1]}", "error": -1}

        try:
            delete = await ModelQuerier(db, MenuModel).where(MenuModel.id == m_id).delete()
            if delete:
                return {"message": f"删除{types_map[menu.types]}成功", "error": 0}
            else:
                return {"message": "删除失败，请稍后重试", "error": -1}
        except Exception as e:
            await db.rollback()
            cls.logger.error(f"删除{types_map[menu.type]}失败: {str(e)}")
            return {"message": "删除失败，请稍后重试", "error": -1}

    @classmethod
    async def check_children(cls, db: AsyncSession, m_id: int) -> bool | None:
        """
        检查是否存在子数据
        :param db: 数据库会话
        :param m_id: 菜单id
        :return: True/False
        """
        return await ModelQuerier(db, MenuModel).where(MenuModel.p_id == m_id).exists()

    @classmethod
    async def get_menus_by_permission(cls, db: AsyncSession, permissions: List[int]):
        """
        根据权限id获取所有权限数据
        :param db:
        :param permissions:
        :return:
        """
        lists = await ModelQuerier(db, MenuModel).where(MenuModel.is_delete == False,
                                                        MenuModel.id.in_(permissions)).order_by(MenuModel.sort.asc(),
                                                                                                MenuModel.id.asc()).get_all()
        unique_menu = list({item.id: item for item in lists}.values())
        return build_tree(unique_menu, parent_id=0)


    @classmethod
    async def get_menu_permission_tree_for_role(cls, db: AsyncSession) -> List[PermissionTree]:
        """
        为角色中的权限树中
        :return:
        """
        lists = await ModelQuerier(db, MenuModel).where(MenuModel.is_delete == False).order_by(MenuModel.sort.asc(),
                                                                                               MenuModel.id.asc()).get_all()
        return await PermissionTree.from_orm(lists)


    @classmethod
    async def get_permission_auth_login(cls, db: AsyncSession, permissions: List[int] = None):
        """
        根据角色中的权限id获取所有权限
        :param db: 数据库会话
        :param permissions: 菜单权
        :return:
        """
        stmt = select(MenuModel).where(
            MenuModel.is_delete == False
        ).order_by(
            MenuModel.sort.asc(),
            MenuModel.id.asc()
        )
        if permissions:
            stmt = stmt.where(MenuModel.id.in_(permissions))

        result = await db.execute(stmt)
        lists = result.scalars().all()

        # 已有菜单id集合
        menu_id_set: Set[int] = set(item.id for item in lists)
        # 上级菜单缓存，避免重复查库
        parent_cache: Dict[int] = {}
        # 权限码和菜单列表
        auth_code: List[str] = []
        auth_menu: List[MenuModel] = []
        for item in lists:
            parent_id = item.p_id
            # 检查parent_id是否存在且不在已查菜单中
            if parent_id and parent_id not in menu_id_set:
                if parent_id not in parent_cache:
                    parent = await ModelQuerier(db, MenuModel).where(MenuModel.id == parent_id,
                                                                     MenuModel.is_delete == False).get_one()
                    if parent:
                        parent_cache[parent_id] = parent
                        auth_menu.append(parent)
            # 分类权限码和菜单
            if item.types == 3:
                if item.code:
                    auth_code.append(item.code)
            else:
                if item.types == 2 and item.code:
                    auth_code.append(item.code)
                auth_menu.append(item)

        # 去重auth_menu，保留顺序
        unique_auth_menu = list({item.id: item for item in auth_menu}.values())
        # 构建树形结构并转换格式
        tree_menu = await cls.__build_tree_menu(unique_auth_menu, parent_id=0)
        return tree_menu, auth_code

    @classmethod
    async def __build_tree_menu(cls, menu_list: List[Any], parent_id=0):
        """
         递归构建树形结构，并转换为前端需要的格式
        :param menu_list:
        :param parent_id:
        :return:
        """
        tree = []
        for item in menu_list:

            if item.p_id == parent_id:
                href = item.url if item.e_link == 1 else None
                if item.p_id == 0:
                    path = "/" + item.name
                    component = "Layout"
                else:
                    path = item.name
                    if item.e_link == 1:
                        component = "IFrame"
                    else:
                        component = item.url
                menu_item = {
                    "path": path,
                    "name": item.name,
                    "meta": {
                        "title": item.label,
                        **({"icon": item.icon} if item.icon else {}),
                        "hide_in_menu": int(item.is_hide),
                        **({"href": href} if href is not None else {})  # 如果href等于 None 则从字段中去掉"href"
                    },
                    "component": component,
                }
                # 递归查找子节点
                children = await cls.__build_tree_menu(menu_list, item.id)
                if children:
                    menu_item["children"] = children
                tree.append(menu_item)
        return tree
